﻿//--------------------------------------------------------------------------------------
// File: DirectXTK3DSceneRenderer.cpp
//
// This is a simple Windows Store app for Windows 8.1 showing use of DirectXTK
//
// http://go.microsoft.com/fwlink/?LinkId=248929
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------

#include "pch.h"
#include "DirectXTK3DSceneRenderer.h"

#include "DDSTextureLoader.h"

#include "..\Common\DirectXHelper.h"	// For ThrowIfFailed and ReadDataAsync
#include "DirectXTex\DirectXTex\DirectXTex.h"
#include "ppl.h"

using namespace SimpleSample;

using namespace DirectX;
using namespace Windows::Foundation;
using namespace Concurrency;

DirectXTK3DSceneRenderer::DirectXTK3DSceneRenderer(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
    m_deviceResources(deviceResources)
{
	CreateDeviceDependentResources();
    CreateWindowSizeDependentResources();
}

// Initializes view parameters when the window size changes.
void DirectXTK3DSceneRenderer::CreateWindowSizeDependentResources()
{
    Size outputSize = m_deviceResources->GetOutputSize();
    float aspectRatio = outputSize.Width / outputSize.Height;
    float fovAngleY = 70.0f * XM_PI / 180.0f;

	// This is a simple example of change that can be made when the app is in
	// portrait or snapped view.
	if (aspectRatio < 1.0f)
	{
		fovAngleY *= 2.0f;
	}

	// Note that the OrientationTransform3D matrix is post-multiplied here
	// in order to correctly orient the scene to match the display orientation.
	// This post-multiplication step is required for any draw calls that are
	// made to the swap chain render target. For draw calls to other targets,
	// this transform should not be applied.

	// This sample makes use of a right-handed coordinate system using row-major matrices.
	m_zNear = .01f;
	m_zFar = 10.f;
	XMMATRIX perspectiveMatrix = XMMatrixPerspectiveFovRH(
		fovAngleY,
		aspectRatio,
		m_zNear,
		m_zFar
		);

	XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();

	XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);

    XMMATRIX projection = XMMatrixMultiply(perspectiveMatrix, orientationMatrix);

    m_batchEffect->SetProjection(projection);

	XMStoreFloat4x4(
        &m_projection,
		projection
		);
}

void DirectXTK3DSceneRenderer::Update(DX::StepTimer const& timer)
{
    XMVECTOR eye = XMVectorSet(0.0f, 0.7f, 1.5f, 0.0f);
    XMVECTOR at = XMVectorSet(0.0f, -0.1f, 0.0f, 0.0f);
    XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

    XMMATRIX view = XMMatrixLookAtRH(eye, at, up);
    XMMATRIX world = XMMatrixRotationY( float(timer.GetTotalSeconds() * XM_PIDIV4) );

    m_batchEffect->SetView(view);
    m_batchEffect->SetWorld(XMMatrixIdentity());

    XMStoreFloat4x4(&m_view, view);
    XMStoreFloat4x4(&m_world, world);
}

void XM_CALLCONV DirectXTK3DSceneRenderer::DrawGrid(FXMVECTOR xAxis, FXMVECTOR yAxis, FXMVECTOR origin, size_t xdivs, size_t ydivs, GXMVECTOR color)
{
    auto context = m_deviceResources->GetD3DDeviceContext();
    m_batchEffect->Apply(context);

    context->IASetInputLayout(m_batchInputLayout.Get());

    m_batch->Begin();

    xdivs = std::max<size_t>(1, xdivs);
    ydivs = std::max<size_t>(1, ydivs);

    for (size_t i = 0; i <= xdivs; ++i)
    {
        float fPercent = float(i) / float(xdivs);
        fPercent = (fPercent * 2.0f) - 1.0f;
        XMVECTOR vScale = XMVectorScale(xAxis, fPercent);
        vScale = XMVectorAdd(vScale, origin);

        VertexPositionColor v1(XMVectorSubtract(vScale, yAxis), color);
        VertexPositionColor v2(XMVectorAdd(vScale, yAxis), color);
        m_batch->DrawLine(v1, v2);
    }

    for (size_t i = 0; i <= ydivs; i++)
    {
        FLOAT fPercent = float(i) / float(ydivs);
        fPercent = (fPercent * 2.0f) - 1.0f;
        XMVECTOR vScale = XMVectorScale(yAxis, fPercent);
        vScale = XMVectorAdd(vScale, origin);

        VertexPositionColor v1(XMVectorSubtract(vScale, xAxis), color);
        VertexPositionColor v2(XMVectorAdd(vScale, xAxis), color);
        m_batch->DrawLine(v1, v2);
    }

    m_batch->End();
}

void DirectXTK3DSceneRenderer::Render()
{
	auto context = m_deviceResources->GetD3DDeviceContext();

	// Set render targets to the screen.
	ID3D11RenderTargetView *const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
	context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

    // Draw procedurally generated dynamic grid
//    const XMVECTORF32 xaxis = { 20.f, 0.f, 0.f };
//    const XMVECTORF32 yaxis = { 0.f, 0.f, 20.f };
//    DrawGrid(xaxis, yaxis, g_XMZero, 20, 20, Colors::Gray);

	Size outputSize = m_deviceResources->GetOutputSize();
	float aspectRatio = outputSize.Width / outputSize.Height;
    // Draw sprite - the depth stencil in the corner
    m_sprites->Begin();
	RECT r;
	r.top = 10;
	r.left = (LONG) (outputSize.Width / 2);
	r.right = (LONG) (r.left + outputSize.Width / 3 + 75);
	r.bottom = (LONG) (outputSize.Height / 3 + 10);
    m_sprites->Draw(m_texture2.Get(), r);

    m_font->DrawString(m_sprites.get(), L"Depth stencil Sample", XMFLOAT2(100, 10), Colors::Yellow);
    m_sprites->End();

    // Draw 3D object
    XMMATRIX world = XMLoadFloat4x4(&m_world);
    XMMATRIX view = XMLoadFloat4x4(&m_view);
    XMMATRIX projection = XMLoadFloat4x4(&m_projection);

    XMMATRIX local = XMMatrixMultiply(world, XMMatrixTranslation(-2.f, -2.f, -4.f));
	m_shape->Draw(local, view, projection, Colors::White, m_texture1.Get());
	m_shape2->Draw(local, view, projection, Colors::White);
}
#undef max
#undef min

void DirectXTK3DSceneRenderer::CaptureDepthStencil()
{

	auto dst = m_deviceResources->GetDepthStencilTexture();
	if (dst == nullptr)
		return;
	ID3D11Resource *resource;
	dst->GetResource(&resource);
	DirectX::ScratchImage img;
	DirectX::CaptureTexture(m_deviceResources->GetD3DDevice(),
		m_deviceResources->GetD3DDeviceContext(), resource, img);
	auto image = img.GetImages();
	float *pSource = (float *)image->pixels;
	DirectX::Image depthImage;
	depthImage.width = image->width;
	depthImage.height = image->height;
	depthImage.format = DXGI_FORMAT_B8G8R8A8_UNORM;
	depthImage.rowPitch = image->width*sizeof(UINT);
	int bufferSize = image->width * image->height;
	depthImage.slicePitch = depthImage.rowPitch * depthImage.height;
	depthImage.pixels = (uint8_t*) new UINT[bufferSize];

	float Zmin = 1.0;
	float Zmax = 0.0;
	std::vector<float> zBuffer(bufferSize);
	parallel_for(0, bufferSize, [&](int i){
		zBuffer[i] = ((-m_zNear*m_zFar) / (m_zFar - m_zNear))
			/ (pSource[i] - 0.5f - (m_zNear + m_zFar) / (2.0f*(m_zFar - m_zNear)));
	});
	for (int i = 0; i < depthImage.height*depthImage.width; i++)
	{
		Zmin = std::min(Zmin, zBuffer[i]);
		if (zBuffer[i]+1<m_zFar)
			Zmax = std::max(Zmax, zBuffer[i]);
	}
	UINT *pixels = (UINT *)depthImage.pixels;
	parallel_for(0, bufferSize, [&](int i){
		if (zBuffer[i] > Zmax){
			pixels[i] = 0x0;
		}
		else {
			float scale = 1.f - (zBuffer[i] - Zmin) / (Zmax - Zmin);
			int color = scale * 255.0;
			pixels[i] = color + 256 * (color + 256 * color);
		}
	});
	DirectX::ScratchImage si;
	si.InitializeFromImage(depthImage);
	auto hr = DirectX::CreateShaderResourceView(m_deviceResources->GetD3DDevice(),
		si.GetImage(0, 0, 0), 1, si.GetMetadata(),
		&m_texture2);
	//	auto applicationData = ApplicationData::Current;
	//	std::wstring path = applicationData->TemporaryFolder->Path->Data();
	//	path += L"\\sample.jpg";
	//	HRESULT hr=DirectX::SaveToWICFile(depthImage, 
	//		DirectX::WIC_FLAGS_NONE, 
	//		DirectX::GetWICCodec(DirectX::WIC_CODEC_JPEG), 
	//		path.c_str());
}

void DirectXTK3DSceneRenderer::CreateDeviceDependentResources()
{
    // Create DirectXTK objects
    auto device = m_deviceResources->GetD3DDevice();
    m_states.reset(new CommonStates(device));

    auto fx = new EffectFactory( device );
    fx->SetDirectory( L"Assets" );
    m_fxFactory.reset( fx );

    auto context = m_deviceResources->GetD3DDeviceContext();
    m_sprites.reset(new SpriteBatch(context));
    m_batch.reset(new PrimitiveBatch<VertexPositionColor>(context));

    m_batchEffect.reset(new BasicEffect(device));
    m_batchEffect->SetVertexColorEnabled(true);

    {
        void const* shaderByteCode;
        size_t byteCodeLength;

        m_batchEffect->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength);

        DX::ThrowIfFailed(
            device->CreateInputLayout(VertexPositionColor::InputElements,
            VertexPositionColor::InputElementCount,
            shaderByteCode, byteCodeLength,
            m_batchInputLayout.ReleaseAndGetAddressOf())
            );
    }

    m_font.reset(new SpriteFont(device, L"assets\\italic.spritefont"));

    //m_shape = GeometricPrimitive::CreateTeapot(context, 4.f, 8);
	m_shape = GeometricPrimitive::CreateIcosahedron(context, 2.f, true);
	m_shape2 = GeometricPrimitive::CreateCube(context, 1.f, true);

    // SDKMESH has to use clockwise winding with right-handed coordinates, so textures are flipped in U
//    m_model = Model::CreateFromSDKMESH(device, L"assets\\tiny.sdkmesh", *m_fxFactory);

    // Load textures
    DX::ThrowIfFailed(
        CreateDDSTextureFromFile(device, L"assets\\seafloor.dds", nullptr, m_texture1.ReleaseAndGetAddressOf())
        );

    DX::ThrowIfFailed(
        CreateDDSTextureFromFile(device, L"assets\\windowslogo.dds", nullptr, m_texture2.ReleaseAndGetAddressOf())
        );
}

void DirectXTK3DSceneRenderer::ReleaseDeviceDependentResources()
{
    m_states.reset();
    m_fxFactory.reset();
    m_sprites.reset();
    m_batch.reset();
    m_batchEffect.reset();
    m_font.reset();
    m_shape.reset();
    m_model.reset();
    m_texture1.Reset();
    m_texture2.Reset();
    m_batchInputLayout.Reset();
}